package jm.easyconfig;

import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Random;

import org.springframework.util.StringUtils;

public class AdminUser implements Serializable {
	private static final long serialVersionUID = 1L;
	public String username;
	public String remarks;
	public String password;
	public String salt;

	public int func;
	public AuthItem[] acl;

	static public int AUTH_READ = 1;
	static public int AUTH_CHANGE = 2;
	static public int AUTH_PUBLISH = 4;

	static public int AUTH_ALL = AUTH_READ | AUTH_CHANGE | AUTH_PUBLISH;

	static public int FUNC_ADMIN = 8;
	
	static public int AUTH_SUPER = AUTH_ALL | FUNC_ADMIN;

	static public int DATA_CONTROL = 16;

	static public int[] AUTH_CONTROLS = { 1, 2, 4, 8, 16 };
	static public String[] AUTH_CONTROL_NAMES = { "READ", "CHANGE", "PUBLISH", "ADMIN", "DATA" };

	AdminUser buildFrom(AdminUser from) {
		if (from == null)
			return this;

		this.username = from.username;
		if (StringUtils.hasLength(from.password)) {
			this.salt = ((Integer) new Random().nextInt()).toString();
			this.password = CfgHelper.hash(from.password + this.salt);
		}

		this.func = from.func;
		this.acl = from.acl;
		this.remarks = from.remarks;

		return this;
	}

	static AdminUser unknown;
	static {
		unknown = new AdminUser();
		unknown.username = "unkown";
		unknown.password = "36ed89e19c06c591d613a2753a4a8734";
		unknown.salt = "sleepywang1024";
		unknown.func = AdminUser.AUTH_ALL;
		unknown.acl = new AuthItem[0];
	}

	static String funcName(int auth, String joint) {
		List<String> list = new LinkedList<String>();

		for (int i = 0; i < AUTH_CONTROLS.length; i++) {
			if ((auth & AUTH_CONTROLS[i]) == AUTH_CONTROLS[i])
				list.add(AUTH_CONTROL_NAMES[i]);
		}

		return String.join(joint, list);
	}

	void funcRequireThese(int auth) {
		if ((this.func & auth) != auth) {
			throw new AppIllegalAccessException("lack auth of " + funcName(auth, " & "));
		}
	}

	void funcRequireAny(int auth) {
		if ((this.func & auth) == 0) {
			throw new AppIllegalAccessException("lack auth of " + funcName(auth, " | "));
		}
	}

	void itemRequireThese(List<? extends ConfigItem> list, int auth) {
		List<? extends ConfigItemCore> newList = itemFilteredBy(list, auth, true);

		if (newList.size() < list.size()) {
			throw new AppIllegalAccessException(String.format("[%s] records lack auth of %s ",
					list.size() - newList.size(), funcName(auth, " & ")));
		}
	}

	List<? extends ConfigItem> itemFilteredBy(List<? extends ConfigItem> list, int authToCheck, boolean isAnd) {
		LinkedList<ConfigItem> newList = new LinkedList<>();
		HashMap<String, ConfigItem> existed = new HashMap<String, ConfigItem>();

		for (ConfigItem item : list) {
			if (item.item_schema == null || !Objects.equals(true, item.item_schema.get("$safe")))
				continue;

			existed.put(item.key(), item);
			newList.add(item);
		}

		boolean has = isAnd ? (this.func & authToCheck) == authToCheck : (this.func & authToCheck) != 0;

		if (!has)
			return newList;

		if ((this.func & DATA_CONTROL) == 0)
			return list;

		for (AuthItem act : this.acl) {
			has = isAnd ? (act.a & authToCheck) == authToCheck : (act.a & authToCheck) != 0;
			if (!has)
				continue;

			if (act.i.equals("*")) {
				for (ConfigItem item : list) {
					if (existed.containsKey(item.key()))
						continue;

					existed.put(item.key(), item);
					newList.add(item);
				}
			} else if (act.i.endsWith("*")) {
				String prefix = act.i.substring(0, act.i.length() - 1);

				for (ConfigItem item : list) {

					if (!item.item_code.startsWith(prefix))
						continue;

					if (existed.containsKey(item.key()))
						continue;

					existed.put(item.key(), item);
					newList.add(item);
				}

			} else {
				for (ConfigItem item : list) {
					if (!item.item_code.equals(act.i))
						continue;

					if (existed.containsKey(item.key()))
						continue;

					existed.put(item.key(), item);
					newList.add(item);
				}
			}

		}

		return newList;
	}

	void changeRequire(List<? extends ChangeLogItem> list, int authToCheck, boolean isAnd) {

		HashMap<String, ChangeLogItem> authorized = changeAllowedMapBy(list, authToCheck, isAnd);

		List<ChangeLogItem> failed = new LinkedList<ChangeLogItem>();

		for (ChangeLogItem item : list) {
			if (!authorized.containsKey(item.key()))
				failed.add(item);
		}

		if (failed.size() > 0) {
			String message = String.format("[%s] records lack auth of %s ", failed.size(),
					funcName(authToCheck, isAnd ? " & " : " | "));

			throw new AppIllegalAccessException(message);
		}
	}

	ChangeLogItem[] changeFilteredByThese(ChangeLogItem[] list, int authToCheck) {
		List<ChangeLogItem> allowed = changeFilteredBy(Arrays.asList(list), authToCheck, true);
		return allowed.toArray(new ChangeLogItem[allowed.size()]);
	}

	List<ChangeLogItem> changeFilteredBy(List<? extends ChangeLogItem> list, int authToCheck, boolean isAnd) {

		HashMap<String, ChangeLogItem> authorized = changeAllowedMapBy(list, authToCheck, isAnd);

		List<ChangeLogItem> allowed = new LinkedList<ChangeLogItem>();

		for (ChangeLogItem item : list) {
			if (authorized.containsKey(item.key()))
				allowed.add(item);
		}

		return allowed;
	}

	/*
	 * return: authorized key-to-item map
	 */
	private HashMap<String, ChangeLogItem> changeAllowedMapBy(List<? extends ChangeLogItem> list, int authToCheck,
			boolean isAnd) {
		HashMap<String, ChangeLogItem> authorized = new HashMap<String, ChangeLogItem>();

		for (ChangeLogItem item : list) {
			if (item.oschema != null && Objects.equals(true, item.oschema.get("$safe")))
				authorized.put(item.key(), item);
		}

		boolean has = isAnd ? (this.func & authToCheck) == authToCheck : (this.func & authToCheck) != 0;

		if (!has) {
			return authorized;
		}

		if ((this.func & DATA_CONTROL) == 0) {
			for (ChangeLogItem item : list) {
				authorized.put(item.key(), item);
			}
			return authorized;
		}

		for (AuthItem act : this.acl) {

			has = isAnd ? (act.a & authToCheck) == authToCheck : (act.a & authToCheck) != 0;
			if (!has)
				continue;

			if (act.i.equals("*")) {
				for (ChangeLogItem item : list) {
					authorized.put(item.key(), item);
				}
			} else if (act.i.endsWith("*")) {
				String prefix = act.i.substring(0, act.i.length() - 1);

				for (ChangeLogItem item : list) {
					if (item.code.startsWith(prefix))
						authorized.put(item.key(), item);
				}

			} else {
				for (ChangeLogItem item : list) {
					if (item.code.equals(act.i))
						authorized.put(item.key(), item);
				}
			}

		}

		return authorized;
	}
}

class AuthItem implements Serializable {
	private static final long serialVersionUID = 1L;

	public String i;/* item */
	public int a;/* action */

	AuthItem item(String i) {
		this.i = i;
		return this;
	}

	AuthItem action(int a) {
		this.a = a;
		return this;
	}
}

enum SecurityMode {
	none, self, host, mixture
}